package org.spring.guava.app.config;

import com.google.common.base.Joiner;
import com.google.common.cache.Cache;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.google.common.reflect.Invokable;
import com.google.common.util.concurrent.*;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.spring.guava.domain.user.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
@SpringBootTest
class GuavaConfigTest {
    private static UserEntity userEntity;

    @Autowired
    Cache<String, String> cache;

    @BeforeAll
    static void beforeAll() {
        userEntity = new UserEntity("user", "password", 1000D, new Date());

    }

    @Test
//    @RepeatedTest(5)  // 查看缓存命中率
    public void cacheTest() {
        // 存入缓存
        cache.put("user:1", userEntity.toString());
        // 如果存在就返回
        log.info(cache.getIfPresent("user:1"));
        // 删除缓存，无参方法将会清空整个缓存
        cache.invalidate("user:1");
        // 返回 null
        log.info(cache.getIfPresent("user:1"));
        // 获取缓存的统计信息
        log.info(cache.stats().toString());
    }

    @Test
    void joinerTest() {
        // 酷似 Python 中的 ';'.join([1, 2, 3, 4, 5, 6, 7, 8, 9])
        Joiner joiner = Joiner.on(';').skipNulls();
        String join = joiner.join(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9));
        log.info(join);
    }

    @Test
    // 消除 @Beta 的警告信息
    @SuppressWarnings("UnstableApiUsage")
    public void bloomFilterTest() {
        BloomFilter<String> bloomFilter = BloomFilter.create(
                Funnels.stringFunnel(Charset.defaultCharset()), // 哈希函数
                1000,  // 0 1 数组大小
                0.01);  // 允许 1% 的误判率
        bloomFilter.put("apple");
        bloomFilter.put("banana");
        bloomFilter.put("orange");

        // 进行判断
        log.info(String.valueOf(bloomFilter.mightContain("apple")));   // true
        log.info(String.valueOf(bloomFilter.mightContain("banana")));   // true
        log.info(String.valueOf(bloomFilter.mightContain("orange")));   // true
        log.info(String.valueOf(bloomFilter.mightContain("grape")));   // true

        // 输出统计信息
        log.info("FPP: {}", bloomFilter.expectedFpp());
        log.info("Elements Count: {}", bloomFilter.approximateElementCount());
    }

    @Test
    public void eventBusTest() {
        // 发布
        EventBus eventBus = new EventBus();
        // 订阅
        eventBus.register(new Listener());
        // 发布消息
        eventBus.post("订单号：100001");
    }

    static class Listener {
        // 订阅
        @Subscribe
        public void handleEvent(String orderId) {
            log.info(orderId);
        }
    }

    @Test
    public void invokableTest() throws NoSuchMethodException {
        Method method = UserEntity.class.getMethod("setUserName", String.class);
        Invokable<?, ?> invokable = Invokable.from(method);
        log.info("方法名：{}", invokable.getName());
        log.info("参数类型：{}", Arrays.toString(invokable.getTypeParameters()));
        log.info("是否是 static：{}", invokable.isStatic());
        log.info("是否是可重写：{}", invokable.isOverridable());
    }

    @Test
    @DisplayName("ListenableFuture 会产生回调地狱，请使用 CompleteFuture 替代")
    public void listenableFutureTest() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
        ListenableFuture<String> explosion = executorService.submit(() -> "finished");

        ExecutorService callBackService = Executors.newFixedThreadPool(1);
        Futures.addCallback(explosion, new FutureCallback<>() {
            public void onSuccess(String explosion) {
                System.out.println(explosion);
                System.out.println("onSuccess");
                countDownLatch.countDown();
            }

            public void onFailure(@NonNull Throwable thrown) {
                System.out.println("onFailure");
                countDownLatch.countDown();
            }
        }, callBackService);

        countDownLatch.await();
    }


    @Test
    @DisplayName("回调地狱")
    void callBackTest() {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
        ListenableFuture<String> step1 = listeningExecutorService.submit(() -> {
            System.out.println("step 1");
            return "step 1 ";
        });
        ListenableFuture<String> step2 = listeningExecutorService.submit(() -> {
            System.out.println("step 2");
            return "step 2 ";
        });

        ListenableFuture<List<String>> allAsList = Futures.allAsList(step1, step2);
        Futures.addCallback(allAsList, new FutureCallback<>() {
            @Override
            public void onSuccess(List<String> result) {
                System.out.println(result);
                ListenableFuture<String> step3 = listeningExecutorService.submit(() -> {
                    System.out.println("step 3");
                    return "step 3 ";
                });

                Futures.addCallback(step3, new FutureCallback<>() {
                    @Override
                    public void onSuccess(String result) {
                        System.out.println(result);
                    }

                    @Override
                    public void onFailure(Throwable t) {

                    }
                }, listeningExecutorService);
            }

            @Override
            public void onFailure(@NonNull Throwable t) {

            }
        }, listeningExecutorService);
    }
}